; M3 Boot Sector/Loader
;
; Assembled from various sources around the internet by Peter C. de Tagyos
; March 2009
;
; For more information, visit the M3 Design and Development blog:
;		http://m3os.wordpress.com
;
;	It is assumed that the target PC sports at least an 80386 processor.
;	I mean, c'mon - it's 2009! I don't want to waste time or code on anything less.
;

;==============================================================================
;	Constants
;==============================================================================

KERNEL_ADDR 	EQU	0x1000		; Address that the kernel will be loaded to by
								; the boot loader
CODE_SEG_ID 	EQU 0x08		; Code segment ID is 8
DATA_SEG_ID 	EQU 0x10		; Data segment ID is 16
STACK_ADDR 		EQU 0x90000		; Top of conventional memory


;==============================================================================
;	16-bit code - The CPU boots in Real mode, which is 16-bit							;==============================================================================

[BITS 16]       

[ORG 0x7C00]    ; The BIOS loads the boot sector into memory location 0x7C00


	; The first thing that we want to do is load the kernel from disk into memory.
	; We'll do this using BIOS interrupt 13h.
	;	ref: http://www.osdever.net/tutorials/brunmar/tutorial_02.php
	;	ref: http://en.wikipedia.org/wiki/INT_13

reset_drive:
	mov ah, 0               ; RESET-command
	int 13h                 ; Call interrupt 13h
	or ah, ah               ; Check for error code
	jnz reset_drive         ; Try again if ah != 0

	xor ax, ax
	mov es, ax
	mov bx, KERNEL_ADDR     ; BX holds the destination address

	mov ah, 02h             ; READ SECTOR-command
	mov al, 08h             ; Number of sectors to read = 8
	mov ch, 0               ; Cylinder = 0
	mov cl, 02h             ; Sector = 2 (boot sector is the first sector)
	mov dh, 0               ; Head = 0
	int 13h                 

	or ah, ah               ; Check for error code
	jnz reset_drive         ; Try again if ah != 0

	; Now we want to enable the A20 gate on the system bus. This will allow 
	; us to address memory above 1MB, and will get us ready to enter 
	; protected mode.

	call enable_A20

	; OK - now we want to enter protected mode. We need to turn off the 
	; interrupts, load up the Global Descriptor Tables that we've declared 
	; down in the data section of our code, and set the first bit of the 
	; CR0 register. Finally, we'll need to do a far jump to clear out the 
	; prefetch queue.
	;	ref: http://www.osdever.net/tutorials/brunmar/tutorial_02.php

	cli                     ; we don't want to be interrupted right now

	xor ax, ax
	mov ds, ax              ; clear ds - this register is used by lgdt

	lgdt [gdt_desc]         ; Load the GDT descriptor

	mov eax, cr0            ; Copy the contents of CR0 into EAX
	or eax, 1               ; Set bit 0
	mov cr0, eax            ; Copy the contents of EAX into CR0

	jmp CODE_SEG_ID:clear_q      ; do a far jump to clear the prefetch queue


;==============================================================================
;	Enable A20 gate - allows us to address memory above 1MB
; 	This method of enabling A20 uses I/O with keyboard controller chip.
;		ref: http://wiki.osdev.org/A20_Line
;==============================================================================

enable_A20:
	cli

	call a20wait
	mov al, 0xAD
	out 0x64, al

	call a20wait
	mov al, 0xD0
	out 0x64, al

	call a20wait2
	in al, 0x60
	push eax

	call a20wait
	mov al, 0xD1
	out 0x64, al

	call a20wait
	pop eax
	or al, 2
	out 0x60, al

	call a20wait
	mov al, 0xAE
	out 0x64, al

	call a20wait
	sti
	ret

a20wait:
	in al, 0x64
	test al, 2
	jnz a20wait
	ret

a20wait2:
	in al, 0x64
	test al, 1
	jz a20wait2
	ret


;==============================================================================
;	32-bit code - used once CPU is in protected mode
;==============================================================================

[BITS 32]

clear_q:
	; The first order of business is to set up the registers properly
	; Set up the stack and data segments, and point at the screen buffer

	; According to the GDT we defined, the Code Segment descriptor is 8 (0x08)
	; and the data/stack segment descriptor is 16 (0x10). Point all segment 
	; registers except for cs at the data segment.

	mov ax, DATA_SEG_ID	
	mov ds, ax
	mov es, ax
	mov fs, ax
	mov gs, ax
	mov ss, ax

	; Point the stack pointer at the stack
	mov esp, STACK_ADDR	

	jmp CODE_SEG_ID:KERNEL_ADDR	; We're done - JUMP TO THE KERNEL!


;==============================================================================
;	Data - all predefined data is declared here
;==============================================================================

; Global Descriptor Table - this particular GDT sets up a "Basic Flat" memory
; model, in which the code segment and the data segment are mapped to the 
; entire linear address space.
;	ref: http://www.osdever.net/tutorials/brunmar/tutorial_02.php
; 	ref: IA-32 Software Developer's Manual - Volume 3 - Section 3.2

gdt:                    ; Address for the GDT

gdt_null:               ; Null Segment
        dd 0
        dd 0

gdt_code:               ; Code segment, read/execute, nonconforming
        dw 0FFFFh
        dw 0
        db 0
        db 10011010b
        db 11001111b
        db 0

gdt_data:               ; Data segment, read/write, expand down
        dw 0FFFFh
        dw 0
        db 0
        db 10010010b
        db 11001111b
        db 0
gdt_end:                ; Used to calculate the size of the GDT

gdt_desc:                       ; The GDT descriptor
        dw gdt_end - gdt - 1    ; Limit (size)
        dd gdt                  ; Address of the GDT


;==============================================================================
;	Filler & Boot Signature
;==============================================================================

	times 510-($-$$) db 0		; Pad with zeroes to ensure boot sector 
								; is exactly 512 bytes

	dw 0xAA55					; The last two bytes are the boot sector sig
